package com.kakarote.oa.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.kakarote.common.field.entity.ModelFieldExtend;
import com.kakarote.common.field.utils.FieldUtil;
import com.kakarote.common.log.entity.OperationLog;
import com.kakarote.core.common.Const;
import com.kakarote.core.common.cache.CrmCacheKey;
import com.kakarote.core.common.enums.FieldEnum;
import com.kakarote.core.common.enums.SystemCodeEnum;
import com.kakarote.core.entity.BasePage;
import com.kakarote.core.exception.CrmException;
import com.kakarote.core.feign.admin.entity.FileEntity;
import com.kakarote.core.feign.admin.entity.SimpleUser;
import com.kakarote.core.feign.admin.service.AdminFileService;
import com.kakarote.core.feign.admin.service.AdminService;
import com.kakarote.core.servlet.ApplicationContextHolder;
import com.kakarote.core.servlet.BaseServiceImpl;
import com.kakarote.core.utils.BaseUtil;
import com.kakarote.core.utils.UserCacheUtil;
import com.kakarote.core.utils.UserUtil;
import com.kakarote.oa.common.LanguageFieldUtil;
import com.kakarote.oa.common.OaCodeEnum;
import com.kakarote.oa.constart.OaConst;
import com.kakarote.oa.entity.BO.OaPrintTemplateQueryBO;
import com.kakarote.oa.entity.PO.OaLogPrintTemplate;
import com.kakarote.oa.entity.PO.OaLogTemplateFieldExtend;
import com.kakarote.oa.entity.VO.OaLogTemplateFieldVO;
import com.kakarote.oa.mapper.OaLogPrintTemplateMapper;
import com.kakarote.oa.service.IOaLogPrintTemplateService;
import com.kakarote.oa.service.IOaLogService;
import com.kakarote.oa.service.IOaLogTemplateFieldService;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author wwl
 * @date 2022/7/6 18:33
 */
@Service
public class OaLogPrintTemplateServiceImpl extends BaseServiceImpl<OaLogPrintTemplateMapper, OaLogPrintTemplate> implements IOaLogPrintTemplateService {

    @Autowired
    private AdminService adminService;

    @Autowired
    private IOaLogTemplateFieldService oaLogTemplateFieldService;


    /**
     * 分页查询打印模板列表
     *
     * @param templateBO search
     * @return data
     */
    @Override
    public BasePage<OaLogPrintTemplate> queryList(OaPrintTemplateQueryBO templateBO) {
        BasePage<OaLogPrintTemplate> page = this.getBaseMapper().queryList(
                templateBO.parse()
                , templateBO.getConfigId()
                , templateBO.getType()
                , UserUtil.getUserId()
        );
        page.getList().forEach(f -> {
            f.setCreateUserName(ObjectUtil.equal(0L, f.getCreateUserId()) ? "系统默认" : UserCacheUtil.getUserName(f.getCreateUserId()));
            Map<String, String> keyMap = new HashMap<>();
            keyMap.put("configName_resourceKey", "oa.log.template.config." + f.getConfigName());
            f.setLanguageKeyMap(keyMap);
        });
        return page;
    }

    /**
     * 删除打印模板
     *
     * @param templateId templateId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationLog deletePrintTemplate(Long templateId) {
        OaLogPrintTemplate template = getById(templateId);
        OperationLog operationLog = new OperationLog();
        operationLog.setOperationObject(template.getConfigName());
        operationLog.setOperationInfo("删除日志打印模板：" + template.getConfigName());
        removeById(templateId);
        return operationLog;
    }

    /**
     * 查询字段列表
     *
     * @param logTemplateId type
     * @return data
     */
    @Override
    public List<OaLogTemplateFieldVO> queryFields(Long logTemplateId) {
        List<OaLogTemplateFieldVO> fields = oaLogTemplateFieldService.queryField(logTemplateId, null);
        List<Integer> notPrintFields = Arrays.asList(
                FieldEnum.FIELD_GROUP.getType()
                , FieldEnum.BOOLEAN_VALUE.getType()
                , FieldEnum.HANDWRITING_SIGN.getType()
                , FieldEnum.DESC_TEXT.getType()
                // , FieldEnum.DETAIL_TABLE.getType()
                , FieldEnum.ATTENTION.getType()
                , FieldEnum.TAG.getType()
        );
        fields.removeIf(field -> notPrintFields.contains(field.getType()));
        List<OaLogTemplateFieldVO> allFields = new ArrayList<>();
        fields.forEach(f -> {
            allFields.add(f);
            if (ObjectUtil.equal(f.getType(), FieldEnum.DETAIL_TABLE.getType())) {
                List<OaLogTemplateFieldExtend> sonFields = f.getFieldExtendList();
                sonFields.forEach(son -> {
                    OaLogTemplateFieldVO sonVO = BeanUtil.copyProperties(son, OaLogTemplateFieldVO.class);
                    sonVO.setFieldId(son.getId());
                    allFields.add(sonVO);
                });
            }
        });
        allFields.add(new OaLogTemplateFieldVO("createUserName", "创建人"));
        allFields.add(new OaLogTemplateFieldVO("createTime", "创建时间"));
        allFields.add(new OaLogTemplateFieldVO("updateTime", "更新时间"));
        for (OaLogTemplateFieldVO allField : allFields) {
            Map<String, String> keyMap = new HashMap<>();
            keyMap.put("name_resourceKey", "oa.log.template.field." + LanguageFieldUtil.getFieldName(allField.getFieldName()));
            allField.setLanguageKeyMap(keyMap);
        }

        return allFields;
    }


    /**
     * 打印
     *
     * @param templateId templateId
     * @param id         id
     * @return data
     */
    @Override
    public String print(Long templateId, Long id) {
        OaLogPrintTemplate template = getById(templateId);
        if (StrUtil.isEmpty(template.getContent())) {
            throw new CrmException(OaCodeEnum.CRM_PRINT_TEMPLATE_NOT_EXIST_ERROR);
        }
        Long type = template.getConfigId();
        return replaceContent(id, template.getContent(), type);
    }

    /**
     * 预览
     *
     * @param content content
     * @param type    type
     * @return path
     */
    @Override
    public String preview(String content, String type, Long logId) {
        String fileTypeP = "pdf";
        String fileTypeW = "word";
        if (!Arrays.asList(fileTypeP, fileTypeW).contains(type)) {
            throw new CrmException(OaCodeEnum.CRM_PRINT_PRE_VIEW_ERROR);
        }
        String slash = BaseUtil.isWindows() ? "\\" : "/";
        String date = DateUtil.format(new Date(), "yyyyMMdd");
        String folderPath = FileUtil.getTmpDirPath() + "print" + slash + date;
        String UUID = IdUtil.simpleUUID();
        String fileName = UUID + ".pdf";
        FileUtil.mkdir(folderPath + slash);
        String path = folderPath + slash + fileName;
        String htmlName = UUID + ".html";
        String htmlPath = folderPath + slash + htmlName;
        JSONObject object = new JSONObject();
        String html = "<html>\n" +
                "<head>\n" +
                "<style>\n" +
                ".wk-watermark-dom {\n" +
                "-webkit-transform: rotate(-15deg) !important;\n" +
                (fileTypeW.equals(type) ? "display: none !important;\n" : "") +
                "}\n" +
                "body {\n" +
                "  line-height: 1.4;\n" +
                "  padding: 60px;\n" +
                "  width: 595px;\n" +
                "  margin: 0 auto;\n" +
                "  border-radius: 4px;\n" +
                "  background: white;\n" +
                "  min-height: 100%;\n" +
                "}\n" +
                "p { margin: 5px 0;\n" +
                "  line-height: 1.5;\n" +
                "}\n" +
                "table {\n" +
                "  border-collapse: collapse;\n" +
                "}\n" +
                "table th,\n" +
                "table td {\n" +
                "  border: 1px solid #ccc;\n" +
                "  padding: 0.4rem;\n" +
                "}\n" +
                "figure {\n" +
                "  display: table;\n" +
                "  margin: 1rem auto;\n" +
                "}\n" +
                "figure figcaption {\n" +
                "  color: #999;\n" +
                "  display: block;\n" +
                "  margin-top: 0.25rem;\n" +
                "  text-align: center;\n" +
                "}\n" +
                "hr {\n" +
                "  border-color: #ccc;\n" +
                "  border-style: solid;\n" +
                "  border-width: 1px 0 0 0;\n" +
                "}\n" +
                "code {\n" +
                "  background-color: #e8e8e8;\n" +
                "  border-radius: 3px;\n" +
                "  padding: 0.1rem 0.2rem;\n" +
                "}\n" +
                ".mce-content-body:not([dir=rtl]) blockquote {\n" +
                "  border-left: 2px solid #ccc;\n" +
                "  margin-left: 1.5rem;\n" +
                "  padding-left: 1rem;\n" +
                "}\n" +
                ".mce-content-body[dir=rtl] blockquote {\n" +
                "  border-right: 2px solid #ccc;\n" +
                "  margin-right: 1.5rem;\n" +
                "  padding-right: 1rem;\n" +
                "}\n" +
                "</style>\n" +
                "<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">" +
                "</head>\n" +
                "<body>\n" +
                content +
                "</body>\n" +
                "</html>";
        FileUtil.writeUtf8String(html, htmlPath);
        if (fileTypeP.equals(type)) {
            writePDFContent(htmlPath, path);
        } else if (fileTypeW.equals(type)) {
            String filePath = folderPath + slash + UUID + ".doc";
            File file = FileUtil.file(filePath);
            try (
                    ByteArrayInputStream byteArrayInputStream = IoUtil.toUtf8Stream(html);
                    FileOutputStream outputStream = new FileOutputStream(file);
                    POIFSFileSystem poifsFileSystem = new POIFSFileSystem();
            ) {
                DirectoryEntry directoryEntry = poifsFileSystem.getRoot();
                directoryEntry.createDocument("WordDocument", byteArrayInputStream);
                object.put("word", file.getAbsolutePath());
                poifsFileSystem.writeFilesystem(outputStream);
            } catch (Exception e) {
                log.error("打印预览转换word失败", e);
                throw new CrmException(SystemCodeEnum.SYSTEM_ERROR);
            }
            writePDFContent(htmlPath, path);
        }
        String uuid = IdUtil.simpleUUID();
        int holdTimeSeconds = 3600 * 24;
        // 如果有日志id的话，说明是列表中的，量比较大。所以保存时间短一点 4小时
        if (ObjectUtil.isNotNull(logId)) {
            uuid = logId.toString();
            holdTimeSeconds = 3600 * 4;
        }
        object.put("pdf", path);
        BaseUtil.getRedis().setex(CrmCacheKey.CRM_PRINT_TEMPLATE_CACHE_KEY + uuid, holdTimeSeconds, object.toJSONString());
        return uuid;
    }

    private void writePDFContent(String content, String distPath) {
        log.info("准备转换pdf");
        String toPdfTool;
        if (BaseUtil.isWindows()) {
            toPdfTool = OaConst.WIN_TO_PDF_TOOL;
        } else {
            toPdfTool = OaConst.LINUX_TO_PDF_TOOL;
        }
        StringBuilder cmd = new StringBuilder();
        log.info("设置插件地址 ： " + toPdfTool);
        cmd.append(toPdfTool);
        cmd.append(" ");
        cmd.append(" --log-level none ");
        cmd.append(content);
        cmd.append(" ");
        cmd.append(distPath);
        try {
            log.info("进行转换,html地址 ： " + content);
            log.info("进行转换,转换后pdf地址 ： " + distPath);
            Process process = Runtime.getRuntime().exec(cmd.toString());
            process.waitFor();
            log.info("转换成功 ");
            process.destroy();
        } catch (Exception e) {
            log.info("转换失败，失败原因" + e);
        }
    }

    private String replaceContent(Long logId, String content, Long logTemplateId) {
        List<String> spanList = ReUtil.findAllGroup0("<span((?!/span>).)*data-wk-tag=\".*?\".*?</span>", content);
        List<String> tableStyleList = ReUtil.findAllGroup0("<table[^(<|>)]*>", content);
        for (String tableStyle : tableStyleList) {
            String newStyle;
            if (tableStyle.contains("float: right") || tableStyle.contains("margin-left: auto; margin-right: auto;")) {
                newStyle = tableStyle.replace(">", " align=\"right\">");
            } else {
                newStyle = tableStyle.replace(">", " align=\"left\">");
            }
            content = content.replaceAll(tableStyle, newStyle);
        }
        List<String> tableHtmlList = ReUtil.findAllGroup0("<table.*?data-wk-table-tag=\"table\".*?table>", content);
        Map<String, String> map = new HashMap<>();
        // 字段表中的字段
        List<OaLogTemplateFieldVO> fieldList = oaLogTemplateFieldService.queryField(logTemplateId, logId);
        Map<String, Integer> fieldName2TypeMap = fieldList.stream().collect(Collectors.toMap(OaLogTemplateFieldVO::getFieldName, OaLogTemplateFieldVO::getType));
        JSONObject record = ApplicationContextHolder.getBean(IOaLogService.class).queryById(logId);
        spanList.forEach(span -> {
            String key = ReUtil.getGroup1("<span.*?data-wk-tag=\"(.*?)\".*?</span>", span);
            String fieldName = key.split("\\.")[1];
            fieldName = StrUtil.toCamelCase(fieldName);
            String value;
            if (fieldName2TypeMap.containsKey(fieldName)) {
                Integer formType = fieldName2TypeMap.get(fieldName);
                getName(record, fieldName, formType);
            }
            // 字段表中没有的字段
            if (ObjectUtil.isEmpty(record.get(fieldName))) {
                value = "";
            } else {
                if (record.get(fieldName) instanceof Date) {
                    value = DateUtil.formatDateTime((Date) record.get(fieldName));
                } else if (record.get(fieldName) instanceof LocalDateTime) {
                    value = LocalDateTimeUtil.formatNormal((LocalDateTime) record.get(fieldName));
                } else {
                    value = TypeUtils.castToString(record.get(fieldName));
                }
            }
            map.put(key, value);
        });
        content = appendTableContent(record, content, tableHtmlList, fieldList);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String k = entry.getKey();
            String v = StrUtil.isNotEmpty(entry.getValue()) ? entry.getValue() : "";
            content = content.replaceAll("(<span((?!/span>).)*data-wk-tag=\"" + k + "\".*?)(\\{.*?\\})(</span>)", "$1" + v + "$4");
        }
        return content;
    }

    /**
     * 修改表格占位符为对应数据
     *
     * @param record    数据
     * @param content   内容
     * @param tableList 表格html内容
     * @param fields    所有字段(含值)
     * @return 内容
     */
    private String appendTableContent(JSONObject record, String content, List<String> tableList, List<OaLogTemplateFieldVO> fields) {
        if (CollUtil.isEmpty(tableList)) {
            return content;
        }
        // 明细表格字段id -> 字段
        Map<Long, OaLogTemplateFieldVO> fieldId2FieldMap = fields.stream().filter(f -> CollUtil.isNotEmpty(f.getFieldExtendList())).collect(Collectors.toMap(OaLogTemplateFieldVO::getFieldId, Function.identity()));
        for (String tableHtml : tableList) {
            StringBuilder tableContent = new StringBuilder();
            String tableMid = ReUtil.getGroup0("<tr((?!header).)*data-wk-table-tr-tag=\"value\">.*?</tr>?", tableHtml);
            List<String> spanList = ReUtil.findAllGroup0("<span.*?data-wk-table-value-tag=\".*?\".*?</span>", tableMid);
            if (CollUtil.isNotEmpty(spanList)) {
                String spanStr = spanList.get(0);
                String tableKeyStr = ReUtil.getGroup1("<span.*?data-wk-table-value-tag=\"(.*?)\".*?</span>", spanStr);
                // 表格字段id.表格内字段id
                String[] fieldIds = tableKeyStr.split("\\.");
                OaLogTemplateFieldVO tableField = fieldId2FieldMap.get(TypeUtils.castToLong(fieldIds[0]));
                if (ObjectUtil.isNull(tableField)) {
                    throw new CrmException(OaCodeEnum.PRINT_TEMPLATE_NOT_LATEST);
                }
                // 明细表格值
                List<List<ModelFieldExtend>> tableValues = (List<List<ModelFieldExtend>>) tableField.getValue();
                StringBuilder finalTableContent = tableContent;
                tableValues.forEach(fieldList -> {
                    Map<String, String> key2ValueMap = new HashMap<>();
                    Map<Long, ModelFieldExtend> id2PatchMap = fieldList.stream().collect(Collectors.toMap(ModelFieldExtend::getId, Function.identity()));
                    spanList.forEach(span -> {
                        String tableKey = ReUtil.getGroup1("<span.*?data-wk-table-value-tag=\"(.*?)\".*?</span>", span);
                        String tableIdFieldIdStr = ReUtil.delFirst("^[A-Za-z]*\\.", tableKey);
                        String[] sonTableIdFieldId = tableIdFieldIdStr.split("\\.");
                        ModelFieldExtend fieldPatch = id2PatchMap.get(TypeUtils.castToLong(sonTableIdFieldId[1]));
                        if (ObjectUtil.isNotNull(fieldPatch)) {
                            String replaceValue = FieldUtil.parseDetailTableField(fieldPatch);
                            key2ValueMap.put(tableKey, replaceValue);
                        }
                    });
                    String newTableMid = tableMid;
                    for (Map.Entry<String, String> entry : key2ValueMap.entrySet()) {
                        String k = entry.getKey();
                        String v = StrUtil.isNotEmpty(entry.getValue()) ? entry.getValue() : "";
                        newTableMid = newTableMid.replaceAll("(<span((?!/span>).)*data-wk-table-value-tag=\"" + k + "\".*?)(\\{.*?\\})(</span>)", "$1" + v + "$4");
                    }
                    finalTableContent.append(newTableMid);
                });
            }
            tableContent = new StringBuilder(tableHtml.replaceAll("<tr.*?data-wk-table-tr-tag=\"value\">[\\s\\S]*</tr>?", tableContent.toString()));
            content = content.replace(tableHtml, tableContent.toString());
        }
        return content;
    }

    private void getName(Map<String, Object> record, String fieldName, Integer formType) {
        Object value = record.get(fieldName);
        if (ObjectUtil.isNotEmpty(value)) {
            if (formType.equals(FieldEnum.USER.getType())) {
                if (value instanceof Long) {
                    record.put(fieldName, UserCacheUtil.getUserName((Long) value));
                } else if (value instanceof String) {
                    List<SimpleUser> data = UserCacheUtil.getSimpleUsers(StrUtil.splitTrim((CharSequence) value, Const.SEPARATOR).stream().map(Long::valueOf).collect(Collectors.toList()));
                    record.put(fieldName, data.stream().map(SimpleUser::getRealname).collect(Collectors.joining(Const.SEPARATOR)));
                }
            } else if (formType.equals(FieldEnum.STRUCTURE.getType())) {
                if (value instanceof Long) {
                    record.put(fieldName, UserCacheUtil.getDeptName((Long) value));
                } else if (value instanceof String) {
                    record.put(fieldName, UserCacheUtil.getDeptNameList(StrUtil.splitTrim((CharSequence) value, Const.SEPARATOR)));
                }
            } else if (formType.equals(FieldEnum.FILE.getType())) {
                List<FileEntity> data = ApplicationContextHolder.getBean(AdminFileService.class).queryFileList((String) value).getData();
                record.put(fieldName, data.stream().map(FileEntity::getName).collect(Collectors.joining(Const.SEPARATOR)));
            } else if (formType.equals(FieldEnum.AREA_POSITION.getType())) {
                JSONArray array = JSON.parseArray((String) value);
                StringBuilder str = new StringBuilder();
                for (int i = 0; i < array.size(); i++) {
                    JSONObject json = array.getJSONObject(i);
                    if (str.length() != 0) {
                        str.append(",");
                    }
                    str.append(json.getString("name"));
                }
                record.put(fieldName, str.toString());
            } else if (formType.equals(FieldEnum.CURRENT_POSITION.getType())) {
                JSONObject json = JSON.parseObject((String) value);
                record.put(fieldName, json.get("address"));
            }
        } else {
            record.put(fieldName, "");
        }
    }


    /**
     * 复制模板
     *
     * @param templateId templateId
     * @return
     */
    @Override
    public OperationLog copyTemplate(Long templateId) {
        OaLogPrintTemplate crmPrintTemplate = getById(templateId);
        String name = crmPrintTemplate.getTemplateName();
        String pre = ReUtil.delFirst("[(]\\d+[)]$", name);
        List<String> nameList;
        String regex = "^[(]\\d+[)]$";
        if (!ReUtil.contains(regex, name)) {
            nameList = query().like("template_name", pre).list().stream().map(OaLogPrintTemplate::getTemplateName).collect(Collectors.toList());
        } else {
            nameList = query().last("  where template_name regexp '^[(]\\d+[)]$'").list().stream().map(OaLogPrintTemplate::getTemplateName).collect(Collectors.toList());
        }
        StringBuilder numberSb = new StringBuilder();
        for (String dbName : nameList) {
            String endCode = ReUtil.get("[(]\\d+[)]$", dbName, 0);
            if (endCode != null) {
                numberSb.append(endCode);
            }
        }
        int i = 1;
        if (numberSb.length() == 0) {
            String leftIndex = "(";
            String rightIndex = ")";
            while (numberSb.toString().contains(leftIndex + i + rightIndex)) {
                i++;
            }
        }
        crmPrintTemplate.setTemplateName(pre + numberSb + "(1)");
        crmPrintTemplate.setTemplateId(null);
        save(crmPrintTemplate);

        OperationLog operationLog = new OperationLog();
        operationLog.setOperationInfo("复制打印模板：" + name);
        operationLog.setOperationObject(crmPrintTemplate.getTemplateName());
        return operationLog;
    }

    @Override
    public OperationLog add(OaLogPrintTemplate template) {
        // 是否管理员配置的
        template.setCreateUserId(ObjectUtil.equal(1, template.getSystemFlag()) ? 0L : UserUtil.getUserId());
        template.setUpdateTime(LocalDateTimeUtil.now());
        save(template);

        OperationLog operationLog = new OperationLog();
        operationLog.setOperationInfo("新建打印模板：" + template.getTemplateName());
        operationLog.setOperationObject(template.getTemplateName());
        return operationLog;
    }


}
